package ws.jdbc.concurrent.driver.sqlserver.cursor.individual;

import ws.jdbc.concurrent.driver.sqlserver.CResultSet;
import ws.jdbc.concurrent.driver.sqlserver.ResultSetCursor;
import java.sql.SQLException;
import java.util.concurrent.locks.ReentrantLock;
import ws.jdbc.concurrent.driver.sqlserver.ResultSetScroll;
import ws.jdbc.concurrent.driver.sqlserver.tds.ITDSResultSet;
import ws.jdbc.concurrent.driver.sqlserver.tds.TDSMessage;
import ws.jdbc.concurrent.driver.sqlserver.tds.TokenCollection;
import static ws.jdbc.concurrent.driver.sqlserver.tds.rpc.StoredProcedures.*;

/**
 * This class provides the functionality of a result set cursor, with individual cache.
 * 
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public abstract class CursorIndividualCache extends ResultSetCursor {

  public CursorIndividualCache(final TDSMessage message, final CResultSet crs, final ReentrantLock lock) throws SQLException {
    super(message, crs, lock);
    /* Create own cache. */
    tdsResultSet = new TokenCollection(tdsResultSet.getCursor(), tdsResultSet.getColumnMetadata());
  }

  @Override
  public void refreshRow() throws SQLException {
    lock.lock();
    try {
      message.cursorFetch(tdsResultSet.getCursor(), SP_CURSORFETCH_FETCHTYPE_REFRESH, 0, 0); // the last two parameters are ignored by the REFRESH fetch.
      tdsResultSet = message.cursorFetch(tdsResultSet.getCursor(), SP_CURSORFETCH_FETCHTYPE_ABSOLUTE, currentRow, fetchSize);
    } finally {
      lock.unlock();
    }
    rsIndex = 0;
    forwardCache();
  }

  @Override
  public boolean next() throws SQLException {
    ResultSetScroll next = new ResultSetScroll() {

      public void calcCurrentRow() {
        ++currentRow;
      }

      public void calcRSIndex() {
        ++rsIndex;
      }

      public void resetCache() {
        rsIndex = 0;
        forwardCache();
      }

      public void fetch() throws SQLException {
        tdsResultSet = message.cursorFetch(tdsResultSet.getCursor(), SP_CURSORFETCH_FETCHTYPE_ABSOLUTE, currentRow, fetchSize);
      }
    };
    return scroll(next);
  }

  @Override
  public boolean previous() throws SQLException {
    ResultSetScroll previous = new ResultSetScroll() {

      public void calcCurrentRow() {
        --currentRow;
      }

      public void calcRSIndex() {
        --rsIndex;
      }

      public void resetCache() throws SQLException {

        rsIndex = tdsResultSet.rowCount() - 1;
        if ((currentRow + 1) == ROW_AFTER) {
          currentRow = lastRow.get();
        }
        backwardCache();
      }

      public void fetch() throws SQLException {
        ITDSResultSet tmp = message.cursorFetch(tdsResultSet.getCursor(), SP_CURSORFETCH_FETCHTYPE_ABSOLUTE, currentRow, 1);
        tdsResultSet = message.cursorFetch(tdsResultSet.getCursor(), SP_CURSORFETCH_FETCHTYPE_PREV_NOADJUST, currentRow, fetchSize - 1);
        tdsResultSet.addRows(tmp);
      }
    };
    return scroll(previous);
  }

  @Override
  public void insertRow() throws SQLException {
    checkInsertRow();
    
    lock.lock();
    try {
      message.cursorInsert(tdsResultSet.getCursor(), updateValues);
    } finally {
      lock.unlock();
    }
    lastRow.incrementAndGet();
    cancelRowUpdates();
  }

  @Override
  public void updateRow() throws SQLException {
    lock.lock();
    try {
      /* For the update to succeed the server cursor must me moved to currentRow. */
      message.cursorFetch(tdsResultSet.getCursor(), SP_CURSORFETCH_FETCHTYPE_ABSOLUTE, currentRow, fetchSize);
      message.cursorUpdate(tdsResultSet.getCursor(), 1, updateValues);
    } finally {
      lock.unlock();
    }
    /* Update cache with new values. */
    tdsResultSet.updateRow(rsIndex, updateValues);
    cancelRowUpdates();
  }

  /**
   * Scroll the result set.
   *
   * @param impl result set scroll implementation.
   *
   * @return {@code true} if the result set is in a row, otherwise {@code false}.
   *
   * @throws SQLException if an i/o error occurs while reading the result set from the input stream.
   */
  @Override
  protected boolean scroll(final ResultSetScroll impl) throws SQLException {
    lock.lock();
    try {
      impl.calcCurrentRow(); // sometimes it uses message...
    } finally {
      lock.unlock();
    }
    if (currentRow < 1) {
      currentRow = ROW_BEFORE;
      rsIndex = -1;
      return false;
    }
    if (currentRow > lastRow.get()) {
      currentRow = lastRow.get() + 1;
      rsIndex = tdsResultSet.rowCount();
      return false;
    }
    if (tdsResultSet.isEmpty() || !inCache()) {
      lock.lock();
      try {
        impl.fetch();
      } finally {
        lock.unlock();
      }
      impl.resetCache();
    } else {
      impl.calcRSIndex();
    }
    return true;
  }
}
